use std::arch::x86_64::__m128;
use std::cmp::min;
use std::env::var;
use std::path;
use std::path::Path;
use std::str::FromStr;
use clap::{Parser, Subcommand};
use clap::builder::styling::AnsiColor::Yellow;
use image::{GenericImageView, ImageReader, Pixel};
use snafu::{whatever, ResultExt, Snafu};
use crate::AppError::FailedToOpenFile;

#[derive(Subcommand, Debug)]
enum Command{
    /// Crop the transparent pixels at the edges of the image
    Crop,
}

/// A set of utilities to handle images
#[derive(Parser, Debug)]
#[command(version = "0.0.1")]
struct Argument
{
    /// The path of image to handle
    path: String,

    #[command(subcommand)]
    command: Command,
}

#[derive(Debug, Snafu)]
enum AppError{
    #[snafu(display("Failed to find file at path {path}"))]
    FailedToOpenFile {
        source: std::io::Error,
        path: String,
    },
    #[snafu(display("Cannot detect image type from file {path}"))]
    UnknownImageType {
        source: image::error::ImageError,
        path: String,
    },
    
    #[snafu(whatever, display("{message}"))]
    Whatever {
        message: String,
        #[snafu(source(from(Box<dyn std::error::Error>, Some)))]
        source: Option<Box<dyn std::error::Error>>,
    }
}

type Result<T> = core::result::Result<T, AppError>;

async  fn crop(path: &str) -> Result<()>{
    let img = ImageReader::open(path)
        .context(FailedToOpenFileSnafu { path })?
        .decode()
        .context(UnknownImageTypeSnafu { path})?;
    let (dx, dy)  = img.dimensions();
    if dx == 0 || dy == 0
    {
        return Ok(())
    }
    
    let mut min_x = 0;
    let mut min_y = 0;
    let mut max_x = dx - 1;
    let mut max_y = dy - 1;

    let is_transparent = |p: &(u32, u32)| -> bool {
        img.get_pixel(p.0, p.1).0.get(3).unwrap() != &0
    };
    
    // Left -> Right
    if let Some((x, _)) = (0..dx)
        .flat_map(|x| (0..dy).map(move |y| (x, y)))
        .find(is_transparent)
    {
        min_x = x;
    } else {
        return Ok(())
    }
    
    // Right -> Left
    if let Some((x, _)) = (min_x + 1..dx).rev()
        .flat_map(|x| (0..dy).map(move |y| (x, y)))
        .find(is_transparent)
    {
        max_x = x;
    }else{
        max_x = min_x;
    }
    
    // Top -> Bottom
    if let Some((_, y)) = (0..dy)
        .flat_map(|y| (min_x..=max_x).map(move |x| (x, y)))
        .find(is_transparent)
    {
        min_y = y;
    }
    
    // Bottom -> Top
    if let Some((_, y)) = (min_y + 1..dy).rev()
        .flat_map(|y| (min_x..=max_x).map(move |x| (x, y)))
        .find(is_transparent)
    {
        max_y = y;
    }else{
        max_y = min_y;
    }

    img.crop_imm(min_x, min_y, max_x - min_x + 1, max_y - min_y + 1)
        .save(path)
        .whatever_context("Could not save image")?;

    Ok(())
}

#[tokio::main]
async fn main() {
    let arg = Argument::parse();
    println!("{:?}", arg);

    match arg.command { Command::Crop => {
        crop(&arg.path).await.unwrap()
    }}
}